package binder

import (
	"mi/compiler/ast"
	"mi/compiler/diagnostic"
	"mi/compiler/token"
	"mi/compiler/utilities"
	"mi/internal/xpath"
	"reflect"
)

type ActiveLabel struct {
	Name string
	BreakTarget *FlowLabel
	ContinueTarget *FlowLabel
	Referenced bool
}

type ContainerFlags int

const (
	// The current ast is not a container and no container manipulation should happen before
	// recursing into it.
	CFNone ContainerFlags = 1 << iota

	// The current ast is a container.  It should be set as the current container (and block-
	// container) before recursing into it.  The current ast does not have locals.  Examples
	//
	//      Classes ObjectLiterals TypeLiterals Interfaces...
	IsContainer

	// The current ast is a block-scoped-container.  It should be set as the current block-
	// container before recursing into it.  Examples
	//
	//      Blocks (when not parented by functions) Catch clauses For/For-in/For-of statements...
	IsBlockScopedContainer

	// The current ast is the container of a control flow path. The current control flow should
	// be saved and restored and a new control flow initialized within the container.
	IsControlFlowContainer

	IsFunctionLike
	IsFunctionExpression
	HasLocals
	IsInterface
	IsObjectLiteralOrClassExpressionMethod
)

const (
	flowCache = "flow"
	symbolCache = "symbol"
	localsCache = "locals"
	aliasCache = "alias"
	nextContainerCache = "nextContainer"
)

func setNodeSymbol(node ast.Node, symbol *Symbol) {
	node.Set(symbolCache, symbol)
}

func GetSymbolOfNode(node ast.Node) *Symbol {
	symbol, ok := node.Get(symbolCache).(*Symbol)
	if ok {
		return symbol
	}
	return nil
}

func setNodeLocals(node ast.Node, table *SymbolTable){
	node.Set(localsCache, table)
}

func GetLocalsOfNode(node ast.Node) *SymbolTable {
	locals, ok := node.Get(localsCache).(*SymbolTable)
	if ok {
		return locals
	}
	return nil
}

func setNodeAliasDeclaration(node ast.Node, alias *ast.AliasTag) {
	node.Set(localsCache, alias)
}

func GetNodeAliasDeclaration(node ast.Node) *ast.AliasTag {
	aliasTag, ok := node.Get(localsCache).(*ast.AliasTag)
	if ok {
		return aliasTag
	}
	return nil
}

func setNodeNextContainer(node ast.Node, next ast.Node) {
	node.Set(nextContainerCache, next)
}

func GetNextContainerOfNode(node ast.Node) ast.Node{
	next, ok := node.Get(nextContainerCache).(ast.Node)
	if ok {
		return next
	}
	return nil
}

func setNodeFlow(node ast.Node, flow FlowNode) {
	node.Set(flowCache, flow)
}

func GetFlowOfNode(node ast.Node) FlowNode {
	flow, ok := node.Get(flowCache).(FlowNode)
	if ok {
		return flow
	}
	return nil
}

func BindPackage(pkg *ast.Package) {
	var b = new(Binder)

	b.classifiableNames = make(map[string]string)

	b.unreachableFlow = new(UnreachableFlow)
	b.reportedUnreachableFlow = new(UnreachableFlow)

	if GetLocalsOfNode(pkg) == nil {
		setNodeLocals(pkg, NewSymbolTable())
		b.container = pkg
		b.bind(pkg)
		// can't set symbol count of last session file, so do it.
		if b.currentFile != nil {
			b.currentFile.SymbolCount = b.symbolCount
		}
	}
}

type Binder struct {
	currentFile         *ast.SourceFile
	languageVersion     ast.ScriptTarget
	parent              ast.Node
	container           ast.Node
	blockScopeContainer ast.Node
	lastContainer       ast.Node

	// state used by control flow analysis
	currentFlow FlowNode
	currentBreakTarget *FlowLabel
	currentContinueTarget *FlowLabel
	currentReturnTarget *FlowLabel
	currentTrueTarget *FlowLabel
	currentFalseTarget *FlowLabel
	preSwitchCaseFlow FlowNode
	activeLabels []*ActiveLabel
	hasExplicitReturn bool

	// state used for emit helpers
	emitFlags ast.NodeFlags

	symbolCount int
	classifiableNames map[string]string

	unreachableFlow *UnreachableFlow
	reportedUnreachableFlow *UnreachableFlow
}

// Debug support
var globalCount = 1
func (b *Binder) createSymbol(flags SymbolFlags, name string) *Symbol {
	b.symbolCount++
	var s = new(Symbol)
	s.Flags = flags
	s.Name = name
	s.Count = globalCount
	globalCount ++
	return s
}

func (b *Binder) addDeclarationToSymbol(symbol *Symbol, node ast.Declaration, symbolFlags SymbolFlags) {
	symbol.Flags |= symbolFlags
	setNodeSymbol(node, symbol)

	symbol.Declarations = append(symbol.Declarations, node)

	if symbolFlags&HasExports != 0 && symbol.Exports == nil {
		symbol.Exports = NewSymbolTable()
	}

	if symbolFlags&HasMembers != 0 && symbol.Members == nil {
		symbol.Members = NewSymbolTable()
	}

	if symbolFlags&Value != 0 {
		symbol.ValueDeclaration = node
	}
}

// Should not be called on a declaration with a computed property name,
// unless it is a well known Symbol.
func (b *Binder) getDeclarationName(node ast.Declaration) string {
	switch node.(type) {
	case *ast.ConstructorDeclaration:
		return ast.ConstructorName

	default:
		name := ast.GetDeclarationName(node)
		if !isNil(name) {
			return name.Value
		}
	}
	return ""
}

func (b *Binder) getDisplayName(node ast.Declaration) string {
	name := ast.GetDeclarationName(node)
	if !isNil(name) {
		return ast.DeclarationNameToString(name)
	} else {
		return b.getDeclarationName(node)
	}
}

// Declares a Symbol for the ast and adds it to symbols. Reports errors for conflicting identifier names.
// param symbolTable - The symbol table which ast will be added to.
// param parent - ast's parent declaration.
// param ast - The declaration to be added to the symbol table
// param includes - The SymbolFlags that ast has in addition to its declaration type (eg export, ambient, etc.)
// param excludes - The flags which ast cannot be declared alongside in a symbol table. Used to report forbidden declarations.
func (b *Binder) declareSymbol(symbolTable *SymbolTable, parent *Symbol, node ast.Declaration, includes SymbolFlags, excludes SymbolFlags) *Symbol {
	var name = b.getDeclarationName(node)

	var symbol *Symbol
	if len(name) == 0 {
		symbol = b.createSymbol(None, "__missing")
	} else {
		// Check and see if the symbol table already has a symbol with this name.  If not,
		// create a new symbol with this name and add it to the table.  Note that we don't
		// give the new symbol any flags *yet*.  This ensures that it will not conflict
		// with the 'excludes' flags we pass in.
		//
		// If we do get an existing symbol, see if it conflicts with the new symbol we're
		// creating.  For example, a 'var' symbol and a 'class' symbol will conflict within
		// the same symbol table.  If we have a conflict, report the issue on each
		// declaration we have for this symbol, and then create a new symbol for this
		// declaration.
		//
		// Note that when properties declared in Javascript constructors
		// (marked by isReplaceableByMethod) conflict with another symbol, the property loses.
		// Always. This allows the common Javascript pattern of overwriting a prototype method
		// with an bound instance method of the same type `this.method = this.method.bind(this)`
		//
		// If we created a new symbol, either because we didn't have a symbol with this name
		// in the symbol table, or we conflicted with an existing symbol, then just add this
		// ast as the sole declaration of the new symbol.
		//
		// Otherwise, we'll be merging into a compatible existing symbol (for example when
		// you have multiple 'vars' with the same name in the same container).  In this case
		// just add this ast into the declarations list of the symbol.
		for _, symbol := range symbolTable.GetSymbolsOfName(name) {
			// If has Duplicate mark have already reported errors
			if symbol.Flags&excludes != 0 && symbol.Flags&Duplicate == 0{
				includes |= Duplicate

				var name = ast.GetDeclarationName(node)
				if !isNil(name) {
					name.SetParent(node)
				}

				diagnostic.AddDiagnosticToSourceFile(b.currentFile, diagnostic.BindDiagnostics,
					utilities.CreateDiagnosticForNode(ast.FirstValidNode(name, node),
						diagnostic.M_Duplicate_identifier_0, b.getDisplayName(node)))
			}
		}

		symbol = symbolTable.GetSymbol(name, includes)
		if symbol == nil || includes&Merge == 0 {
			symbol = b.createSymbol(None, name)
			symbolTable.AppendSymbol(name, symbol)
		}

		if len(name) > 0 && includes&Classifiable != 0 {
			b.classifiableNames[name] = name
		}
	}

	b.addDeclarationToSymbol(symbol, node, includes)
	symbol.Parent = parent

	return symbol
}

// All container nodes are kept on a linked list in declaration order. This list is used by
// the getLocalNameOfContainer function in the type checker to validate that the local name
// used for a container is unique.
func (b *Binder) bindContainer(node ast.Node, containerFlags ContainerFlags) {
	// Before we recurse into a ast's children, we first save the existing parent, container
	// and block-container.  Then after we pop out of processing the children, we restore
	// these saved values.
	var saveContainer = b.container
	var savedBlockScopeContainer = b.blockScopeContainer

	// Depending on what kind of ast this is, we may have to adjust the current container
	// and block-container.   If the current ast is a container, then it is automatically
	// considered the current block-container as well.  Also, for containers that we know
	// may contain locals, we proactively initialize the .locals field. We do this because
	// it's highly likely that the .locals will be needed to place some child in (for example,
	// a parameter, or variable declaration).
	//
	// However, we do not proactively create the .locals for block-containers because it's
	// totally normal and common for block-containers to never actually have a block-scoped
	// variable in them.  We don't want to end up allocating an object for every 'block' we
	// run into when most of them won't be necessary.
	//
	// Finally, if this is a block-container, then we clear out any existing .locals object
	// it may contain within it.  This happens in incremental scenarios.  Because we can be
	// reusing a ast from a previous compilation, that ast may have had 'locals' created
	// for it.  We must clear this so we don't accidentally move any stale data forward from
	// a previous compilation.
	if containerFlags&IsContainer != 0 {
		b.blockScopeContainer = node
		b.container = b.blockScopeContainer

		if containerFlags&HasLocals != 0 {
			setNodeLocals(b.container, NewSymbolTable())
		}
		b.addToContainerChain(b.container)
	} else if containerFlags&IsBlockScopedContainer != 0 {
		b.blockScopeContainer = node
		setNodeLocals(b.blockScopeContainer, nil)
	}
	if containerFlags&IsControlFlowContainer != 0 {
		var saveCurrentFlow = b.currentFlow
		var saveBreakTarget = b.currentBreakTarget
		var saveContinueTarget = b.currentContinueTarget
		var saveReturnTarget = b.currentReturnTarget
		var saveActiveLabels = b.activeLabels
		var saveHasExplicitReturn = b.hasExplicitReturn
		var isIIFE = containerFlags&IsFunctionExpression != 0 && ast.GetImmediatelyInvokedFunctionExpression(node) != nil
		// A non-async IIFE is considered part of the containing control flow. Return statements behave
		// similarly to break statements that exit to a label just past the statement body.
		if isIIFE {
			b.currentReturnTarget = b.createBranchLabel()
		} else {
			b.currentFlow = new(FlowStart)
			if containerFlags&(IsFunctionExpression|IsObjectLiteralOrClassExpressionMethod) != 0 {
				var flowStart = new(FlowStart)
				flowStart.flags = b.currentFlow.Flags()
				flowStart.Container = node
				b.currentFlow = flowStart
			}
			b.currentReturnTarget = nil
		}
		b.currentBreakTarget = nil
		b.currentContinueTarget = nil
		b.activeLabels = nil
		b.hasExplicitReturn = false
		b.bindChildren(node)
		// Reset all reachability check related flags on ast (for incremental scenarios)
		node.AndFlags(^ast.ReachabilityAndEmitFlags)
		if !IsUnreachableFlow(b.currentFlow) &&
			containerFlags&IsFunctionLike != 0 &&
			ast.NodeIsPresent(ast.GetFunctionLikeBody(node)) {
			node.OrFlags(ast.HasImplicitReturn)
			if b.hasExplicitReturn {
				node.OrFlags(ast.HasExplicitReturn)
			}
		}
		if ast.IsSourceFile(node) {
			node.OrFlags(b.emitFlags)
		}
		if isIIFE {
			b.addAntecedent(b.currentReturnTarget, b.currentFlow)
			b.currentFlow = b.finishFlowLabel(b.currentReturnTarget)
		} else {
			b.currentFlow = saveCurrentFlow
		}
		b.currentBreakTarget = saveBreakTarget
		b.currentContinueTarget = saveContinueTarget
		b.currentReturnTarget = saveReturnTarget
		b.activeLabels = saveActiveLabels
		b.hasExplicitReturn = saveHasExplicitReturn
	} else if containerFlags &IsInterface != 0 {
		b.bindChildren(node)
	} else {
		b.bindChildren(node)
	}
	b.container = saveContainer
	b.blockScopeContainer = savedBlockScopeContainer
}

func (b *Binder) bindChildren(node ast.Node) {
	b.bindChildrenWorker(node)
}

func (b *Binder) bindEach(nodes ast.List) bool {
	if isNil(nodes) {
		return false
	}

	for i := 0; i < nodes.Len(); i ++ {
		b.bind(nodes.NodeAt(i))
	}

	return false
}

func (b *Binder) bindEachChild(node ast.Node) {
	ast.ForEachChild(node, b.bind, b.bindEach)
}

func (b *Binder) bindChildrenWorker(node ast.Node) bool {
	// Set document parent
	for _, document := range node.GetDocuments() {
		b.bind(document)
	}

	if b.checkUnreachable(node) {
		b.bindEachChild(node)
		return false
	}

	switch n := node.(type) {
	case *ast.WhileStatement:
		b.bindWhileStatement(n)
	case *ast.DoStatement:
		b.bindDoStatement(n)
	case *ast.ForStatement:
		b.bindForStatement(n)
	case *ast.ForOfStatement:
		b.bindForOfStatement(n)
	case *ast.IfStatement:
		b.bindIfStatement(n)
	case *ast.ReturnStatement:
		b.bindReturnStatement(n)
	case *ast.ThrowStatement:
		b.bindThrowStatement(n)
	case *ast.BranchStatement:
		b.bindBranchStatement(n)
	case *ast.TryStatement:
		b.bindTryStatement(n)
	case *ast.CatchClause:
		b.bindCatchClause(n)
	case *ast.SwitchStatement:
		b.bindSwitchStatement(n)
	case *ast.CaseClause:
		b.bindCaseClause(n)
	case *ast.CaseBlock:
		b.bindCaseBlock(n)
	case *ast.LabeledStatement:
		b.bindLabeledStatement(n)
	case *ast.PrefixUnaryExpression:
		b.bindPrefixUnaryExpressionFlow(n)
	case *ast.PostfixUnaryExpression:
		b.bindPostfixUnaryExpressionFlow(n)
	case *ast.BinaryExpression:
		b.bindBinaryExpressionFlow(n)
	case *ast.ConditionalExpression:
		b.bindConditionalExpressionFlow(n)
	case *ast.CallExpression:
		b.bindCallExpressionFlow(n)
	case *ast.VariableDeclaration:
		b.bindVariableDeclarationFlow(n)
	default:
		b.bindEachChild(node)
	}

	return false
}

func (b *Binder) isNarrowingExpr(expr ast.Expression) bool {
	switch n := expr.(type) {
	case *ast.Identifier,
		*ast.ThisExpression,
		*ast.SelectorExpression:
		return b.isNarrowableReference(expr)
	case *ast.CallExpression:
		return b.hasNarrowableArg(n)
	case *ast.ParenthesizedExpression:
		return b.isNarrowingExpr(n.Expression)
	case *ast.BinaryExpression:
		return b.isNarrowingBinaryExpr(n)
	case *ast.PrefixUnaryExpression:
		return n.Operator.Token == token.Exclamation &&
			b.isNarrowingExpr(n.Operand)
	}

	return false
}

func (b *Binder) isNarrowableReference(expr ast.Node) bool {
	switch n := expr.(type) {
	case *ast.Identifier:
		return true
	case *ast.ThisExpression:
		return true
	case *ast.SelectorExpression:
		return b.isNarrowableReference(n.Expression)
	}
	return false
}

func (b *Binder) hasNarrowableArg(expr *ast.CallExpression) bool {
	if !isNil(expr.Arguments) {
		for i := 0; i < expr.Arguments.Len(); i ++ {
			if b.isNarrowableReference(expr.Arguments.At(i)) {
				return true
			}
		}
	}

	switch n := expr.Expression.(type) {
	case *ast.SelectorExpression:
		return b.isNarrowableReference(n.Expression)
	}
	return false
}

func (b *Binder) isNarrowingBinaryExpr(expr *ast.BinaryExpression) bool {
	switch expr.Operator.Token {
	case token.Equals:
		return b.isNarrowableReference(expr.Left)
	case token.EqualsEquals,
		token.ExclamationEquals:
		return b.isNarrowableOperand(expr.Left) || b.isNarrowableOperand(expr.Right)
	case token.InstanceOfKeyword:
		return b.isNarrowableOperand(expr.Left)
	case token.Comma:
		return b.isNarrowingExpr(expr.Right)
	}

	return false
}

func (b *Binder) isNarrowableOperand(expr ast.Expression) bool {
	switch n := expr.(type) {
	case *ast.ParenthesizedExpression:
		return b.isNarrowableOperand(n.Expression)
	case *ast.BinaryExpression:
		if n.Operator.Token == token.Equals {
			return b.isNarrowableOperand(n.Left)
		}
	}

	return b.isNarrowableReference(expr)
}

func (b *Binder) createBranchLabel() *FlowLabel {
	var f = new(FlowLabel)
	f.Kind = BranchLabel
	f.Antecedents = nil
	return f
}

func (b *Binder) createLoopLabel() *FlowLabel {
	var f = new(FlowLabel)
	f.Kind = LoopLabel
	return f
}

func (b *Binder) setFlowNodeReferenced(flow FlowNode) {
	// On first reference we set the Referenced flag, thereafter we set the Shared flag
	if flow.Flags()&FFReferenced != 0 {
		flow.orFlags(FFShared)
	} else {
		flow.orFlags(FFReferenced)
	}
}

func (b *Binder) addAntecedent(label *FlowLabel, antecedent FlowNode) {
	if !IsUnreachableFlow(antecedent) {
		for _, f := range label.Antecedents {
			if f == antecedent {
				return
			}
		}

		label.Antecedents = append(label.Antecedents, antecedent)
		b.setFlowNodeReferenced(antecedent)
	}
}

func (b *Binder) createFlowCondition(value bool, antecedent FlowNode, expr ast.Expression) FlowNode {
	if IsUnreachableFlow(antecedent) {
		return antecedent
	}
	if expr == nil {
		if value {
			return antecedent
		} else {
			return b.unreachableFlow
		}
	}

	switch n := expr.(type) {
	case *ast.LiteralExpression:
		if n.Token == token.TrueKeyword && !value ||
			n.Token == token.FalseKeyword && value {
			return b.unreachableFlow
		}
	}

	if !b.isNarrowingExpr(expr) {
		return antecedent
	}
	b.setFlowNodeReferenced(antecedent)

	var flow = new(FlowCondition)
	flow.Value = value
	flow.Expr = expr
	flow.Antecedent = antecedent
	return flow
}

func (b *Binder) createFlowSwitchClause(antecedent FlowNode, switchStmt *ast.SwitchStatement, clauseStart int, clauseEnd int) FlowNode {
	if !b.isNarrowingExpr(switchStmt.Expression) {
		return antecedent
	}
	b.setFlowNodeReferenced(antecedent)
	var flow = new(FlowSwitchClause)
	flow.SwitchStatement = switchStmt
	flow.ClauseStart = clauseStart
	flow.ClauseEnd = clauseEnd
	flow.Antecedent = antecedent
	return flow
}

func (b *Binder) createFlowAssignment(antecedent FlowNode, node ast.Node) FlowNode {
	b.setFlowNodeReferenced(antecedent)
	var flow = new(FlowAssignment)
	flow.Antecedent = antecedent
	flow.Node = node
	return flow
}

func (b *Binder) createFlowArrayMutation(antecedent FlowNode, node ast.Expression) FlowNode {
	b.setFlowNodeReferenced(antecedent)
	var flow = new(FlowArrayMutation)
	flow.Antecedent = antecedent
	flow.Node = node
	return flow
}

func (b *Binder) finishFlowLabel(flow *FlowLabel) FlowNode {
	var antecedents = flow.Antecedents
	if antecedents == nil {
		return b.unreachableFlow
	}
	if len(antecedents) == 1 {
		return antecedents[0]
	}
	return flow
}

func (b *Binder) isStmtCond(node ast.Node) bool {
	var parent = node.Parent()
	switch n := parent.(type) {
	case *ast.IfStatement:
		return n.Condition == node
	case *ast.WhileStatement:
		return n.Condition == node
	case *ast.DoStatement:
		return n.Condition == node
	case *ast.ForStatement:
		return n.Condition == node
	case *ast.ConditionalExpression:
		return n.Condition == node
	}

	return false
}

func (b *Binder) isLogicalExpr(node ast.Node) bool {
	for true {
		switch n := node.(type) {
		case *ast.ParenthesizedExpression:
			node = n.Expression
		case *ast.PrefixUnaryExpression:
			node = n.Operand
		case *ast.BinaryExpression:
			return n.Operator.Token == token.AmpersandAmpersand ||
				n.Operator.Token == token.BarBar
		default:
			return false
		}
	}

	return false
}

func (b *Binder) isTopLevelLogicalExpression(node ast.Node) bool {
	for ast.IsParenthesizedExpression(node.Parent()) ||
		ast.IsPrefixUnaryExpression(node.Parent()) && node.(*ast.PrefixUnaryExpression).Operator.Token == token.Exclamation {
		node = node.Parent()
	}
	return !b.isStmtCond(node) && !b.isLogicalExpr(node.Parent())
}

func (b *Binder) bindCondition(node ast.Expression, trueTarget *FlowLabel, falseTarget *FlowLabel) bool {
	var saveTrueTarget = b.currentTrueTarget
	var saveFalseTarget = b.currentFalseTarget
	b.currentTrueTarget = trueTarget
	b.currentFalseTarget = falseTarget
	b.bind(node)
	b.currentTrueTarget = saveTrueTarget
	b.currentFalseTarget = saveFalseTarget
	if node != nil || !b.isLogicalExpr(node) {
		b.addAntecedent(trueTarget, b.createFlowCondition(true, b.currentFlow, node))
		b.addAntecedent(falseTarget, b.createFlowCondition(false, b.currentFlow, node))
	}
	return false
}

func (b *Binder) bindIterativeStatement(node ast.Statement, breakTarget *FlowLabel, continueTarget *FlowLabel) {
	var saveBreakTarget = b.currentBreakTarget
	var saveContinueTarget = b.currentContinueTarget
	b.currentBreakTarget = breakTarget
	b.currentContinueTarget = continueTarget
	b.bind(node)
	b.currentBreakTarget = saveBreakTarget
	b.currentContinueTarget = saveContinueTarget
}

func (b *Binder) bindWhileStatement(node *ast.WhileStatement) {
	var preWhileLabel = b.createLoopLabel()
	var preBodyLabel = b.createBranchLabel()
	var postWhileLabel = b.createBranchLabel()
	b.addAntecedent(preWhileLabel, b.currentFlow)
	b.currentFlow = preWhileLabel
	b.bindCondition(node.Condition, preBodyLabel, postWhileLabel)
	b.currentFlow = b.finishFlowLabel(preBodyLabel)
	b.bindIterativeStatement(node.Body, postWhileLabel, preWhileLabel)
	b.addAntecedent(preWhileLabel, b.currentFlow)
	b.currentFlow = b.finishFlowLabel(postWhileLabel)
}

func (b *Binder) bindDoStatement(node *ast.DoStatement) {
	var preDoLabel = b.createLoopLabel()
	var enclosingLabeledStatement *ActiveLabel
	if ast.IsLabeledStatement(node.Parent()) && len(b.activeLabels) > 0 {
		enclosingLabeledStatement = b.activeLabels[len(b.activeLabels)-1]
	}

	// if do statement is wrapped in labeled statement then target labels for break/continue with or without
	// label should be the same
	var preConditionLabel *FlowLabel
	if enclosingLabeledStatement != nil {
		 preConditionLabel = enclosingLabeledStatement.ContinueTarget
	} else {
		preConditionLabel = b.createBranchLabel()
	}

	var postDoLabel *FlowLabel
	if enclosingLabeledStatement != nil {
		postDoLabel = enclosingLabeledStatement.BreakTarget
	} else {
		postDoLabel = b.createBranchLabel()
	}
	b.addAntecedent(preDoLabel, b.currentFlow)
	b.currentFlow = preDoLabel
	b.bindIterativeStatement(node.Body, postDoLabel, preConditionLabel)
	b.addAntecedent(preConditionLabel, b.currentFlow)
	b.currentFlow = b.finishFlowLabel(preConditionLabel)
	b.bindCondition(node.Condition, preDoLabel, postDoLabel)
	b.currentFlow = b.finishFlowLabel(postDoLabel)
}

func (b *Binder) bindForStatement(node *ast.ForStatement) {
	var preLoopLabel = b.createLoopLabel()
	var preBodyLabel = b.createBranchLabel()
	var postLoopLabel = b.createBranchLabel()
	b.bind(node.Initializer)
	b.addAntecedent(preLoopLabel, b.currentFlow)
	b.currentFlow = preLoopLabel
	b.bindCondition(node.Condition, preBodyLabel, postLoopLabel)
	b.currentFlow = b.finishFlowLabel(preBodyLabel)
	b.bindIterativeStatement(node.Body, postLoopLabel, preLoopLabel)
	b.bind(node.Incrementor)
	b.addAntecedent(preLoopLabel, b.currentFlow)
	b.currentFlow = b.finishFlowLabel(postLoopLabel)
}

func (b *Binder) bindForOfStatement(node *ast.ForOfStatement) {
	var preLoopLabel = b.createLoopLabel()
	var postLoopLabel = b.createBranchLabel()
	b.addAntecedent(preLoopLabel, b.currentFlow)
	b.currentFlow = preLoopLabel
	b.bind(node.Expression)
	b.addAntecedent(postLoopLabel, b.currentFlow)
	b.bind(node.VariableDeclaration)
	b.bindIterativeStatement(node.Body, postLoopLabel, preLoopLabel)
	b.addAntecedent(preLoopLabel, b.currentFlow)
	b.currentFlow = b.finishFlowLabel(postLoopLabel)
}

func (b *Binder) bindIfStatement(node *ast.IfStatement) {
	var thenLabel = b.createBranchLabel()
	var elseLabel = b.createBranchLabel()
	var postIfLabel = b.createBranchLabel()
	b.bindCondition(node.Condition, thenLabel, elseLabel)
	b.currentFlow = b.finishFlowLabel(thenLabel)
	b.bind(node.ThenStatement)
	b.addAntecedent(postIfLabel, b.currentFlow)
	b.currentFlow = b.finishFlowLabel(elseLabel)
	b.bind(node.ElseStatement)
	b.addAntecedent(postIfLabel, b.currentFlow)
	b.currentFlow = b.finishFlowLabel(postIfLabel)
}

func (b *Binder) bindReturnStatement(node *ast.ReturnStatement) {
	b.bind(node.Expression)
	b.hasExplicitReturn = true
	if b.currentReturnTarget != nil {
		b.addAntecedent(b.currentReturnTarget, b.currentFlow)
	}
	b.currentFlow = b.unreachableFlow
}

func (b *Binder) bindThrowStatement(node *ast.ThrowStatement) {
	b.bind(node.Expression)
	b.currentFlow = b.unreachableFlow
}

func (b *Binder) findActiveLabel(name string) *ActiveLabel {
	if len(b.activeLabels) > 0 {
		for _, label := range b.activeLabels {
			if label.Name == name {
				return label
			}
		}
	}
	return nil
}

func (b *Binder) bindBranchFlow(node *ast.BranchStatement, breakTarget *FlowLabel, continueTarget *FlowLabel) {
	var flowLabel *FlowLabel
	if node.Token == token.BreakKeyword {
		flowLabel = breakTarget
	} else {
		flowLabel = continueTarget
	}

	if flowLabel != nil {
		b.addAntecedent(flowLabel, b.currentFlow)
		b.currentFlow = b.unreachableFlow
	}
}

func (b *Binder) bindBranchStatement(node *ast.BranchStatement) {
	b.bind(node.Label)
	if node.Label != nil {
		var activeLabel = b.findActiveLabel(node.Label.Value)
		if activeLabel != nil {
			activeLabel.Referenced = true
			b.bindBranchFlow(node, activeLabel.BreakTarget, activeLabel.ContinueTarget)
		}
	} else {
		b.bindBranchFlow(node, b.currentBreakTarget, b.currentContinueTarget)
	}
}

func (b *Binder) bindTryStatement(node *ast.TryStatement) {
	var preFinallyLabel = b.createBranchLabel()
	var preTryFlow = b.currentFlow
	// TODO Every statement in try block is potentially an exit point!
	b.bind(node.TryBlock)
	b.addAntecedent(preFinallyLabel, b.currentFlow)

	var flowAfterTry FlowNode = b.currentFlow
	var flowAfterCatch FlowNode = b.unreachableFlow

	if node.CatchClauses != nil {
		b.currentFlow = preTryFlow
		for i := 0; i < node.CatchClauses.Len(); i ++ {
			b.bind(node.CatchClauses.At(i))
		}
		b.addAntecedent(preFinallyLabel, b.currentFlow)

		flowAfterCatch = b.currentFlow
	}
	if node.FinallyBlock != nil {
		// in finally flow is combined from pre-try/flow from try/flow from catch
		// pre-flow is necessary to make sure that finally is reachable even if finally flows in both try and finally blocks are unreachable

		// also for finally blocks we inject two extra edges into the flow graph.
		// first -> edge that connects pre-try flow with the label at the beginning of the finally block, it has lock associated with it
		// second -> edge that represents post-finally flow.
		// these edges are used in following scenario
		// var a; (1)
		// try { a = someOperation(); (2)}
		// finally { (3) console.log(a) } (4)
		// (5) a

		// flow graph for this case looks roughly like this (arrows show ):
		// (1-pre-try-flow) <--.. <-- (2-post-try-flow)
		//  ^                                ^
		//  |*****(3-pre-finally-label) -----|
		//                ^
		//                |-- ... <-- (4-post-finally-label) <--- (5)
		// In case when we walk the flow starting from inside the finally block we want to take edge '*****' into account:
		// since it ensures that finally is always reachable. However when we start outside the finally block and go through label (5)
		// then edge '*****' should be discarded because label 4 is only reachable if post-finally label-4 is reachable
		// Simply speaking code inside finally block is treated as reachable as pre-try-flow
		// since we conservatively assume that any line in try block can throw or return in which case we'll enter finally.:
		// However code after finally is reachable only if control flow was not abrupted in try/catch or finally blocks - it should be composed from
		// final flows of these blocks without taking pre-try flow into account.
		//
		// extra edges that we inject allows to control this behavior
		// if when walking the flow we step on post-finally edge - we can mark matching pre-finally edge as locked so it will be skipped.
		var preFinallyFlow = &PreFinallyFlow{
			Antecedent: preTryFlow,
			Lock:       new(flowLock),
			flowNode:   flowNode{},
		}
		b.addAntecedent(preFinallyLabel, preFinallyFlow)

		b.currentFlow = b.finishFlowLabel(preFinallyLabel)
		b.bind(node.FinallyBlock)
		// if flow after finally is unreachable - keep it
		// otherwise check if flows after try and after catch are unreachable
		// if yes - convert current flow to unreachable
		// i.e.
		// try { return "1" } finally { console.log(1); }
		// console.log(2); // this line should be unreachable even if flow falls out of finally block
		if !IsUnreachableFlow(b.currentFlow) {
			if IsUnreachableFlow(flowAfterTry) && IsUnreachableFlow(flowAfterCatch) {
				if flowAfterTry == b.reportedUnreachableFlow || flowAfterCatch == b.reportedUnreachableFlow {
					b.currentFlow = b.reportedUnreachableFlow
				} else {
					b.currentFlow = b.unreachableFlow
				}
			}
		}
		if !IsUnreachableFlow(b.currentFlow) {
			var afterFinallyFlow = &AfterFinallyFlow{
				Antecedent: b.currentFlow,
			}
			preFinallyFlow.Lock = afterFinallyFlow
			b.currentFlow = afterFinallyFlow
		}
	} else {
		b.currentFlow = b.finishFlowLabel(preFinallyLabel)
	}
}

func (b *Binder) bindCatchClause(node *ast.CatchClause) {
	b.bind(node.VariableDeclaration)
	b.bind(node.Block)
}

func (b *Binder) bindSwitchStatement(node *ast.SwitchStatement) {
	var postSwitchLabel = b.createBranchLabel()
	b.bind(node.Expression)
	var saveBreakTarget = b.currentBreakTarget
	var savePreSwitchCaseFlow = b.preSwitchCaseFlow
	b.currentBreakTarget = postSwitchLabel
	b.preSwitchCaseFlow = b.currentFlow
	b.bind(node.Body)
	b.addAntecedent(postSwitchLabel, b.currentFlow)
	var hasDefault bool
	for i := 0; i < node.Body.Clauses.Len(); i ++ {
		var clause = node.Body.Clauses.At(i).(*ast.CaseClause)
		if clause.Expression == nil {
			hasDefault = true
			break
		}
	}

	// We mark a switch statement as possibly exhaustive if it has no default clause and if all
	// case clauses have unreachable end points (e.g. they all return).:
	node.PossiblyExhaustive = !hasDefault && postSwitchLabel.Antecedents == nil
	if !hasDefault {
		b.addAntecedent(postSwitchLabel, b.createFlowSwitchClause(b.preSwitchCaseFlow, node, 0, 0))
	}
	b.currentBreakTarget = saveBreakTarget
	b.preSwitchCaseFlow = savePreSwitchCaseFlow
	b.currentFlow = b.finishFlowLabel(postSwitchLabel)
}

func (b *Binder) bindCaseBlock(node *ast.CaseBlock) {
	var fallthroughFlow = b.unreachableFlow
	if node.Clauses != nil {
		for i := 0; i < node.Clauses.Len(); i ++ {
			var clauseStart = i
			var clause = node.Clauses.At(i).(*ast.CaseClause)
			for (clause.Statements == nil || clause.Statements.Len() == 0) && i+1 < clause.Statements.Len() {
				b.bind(clause)
				i++
			}

			var preCaseLabel = b.createBranchLabel()
			b.addAntecedent(preCaseLabel, b.createFlowSwitchClause(b.preSwitchCaseFlow, node.Parent().(*ast.SwitchStatement), clauseStart, i+1))
			b.addAntecedent(preCaseLabel, fallthroughFlow)
			b.currentFlow = b.finishFlowLabel(preCaseLabel)
			b.bind(clause)
			//fallthroughFlow = b.currentFlow
			//if b.currentFlow.Flags&ast.FFUnreachable == 0 && i != len(ast.Clauses.Array)-1 && b.options.NoFallthroughCasesInSwitch {
			//	b.errorOnFirstToken(clause.ToNode(), diagnostic.M_Fallthrough_case_in_switch)
			//}
		}
	}
}

func (b *Binder) bindCaseClause(node *ast.CaseClause) {
	var saveCurrentFlow = b.currentFlow
	b.currentFlow = b.preSwitchCaseFlow
	b.bind(node.Expression)
	b.currentFlow = saveCurrentFlow
	b.bindEach(node.Statements)
}

func (b *Binder) pushActiveLabel(name string, breakTarget *FlowLabel, continueTarget *FlowLabel) *ActiveLabel {
	var activeLabel = &ActiveLabel{
		Name:           name,
		BreakTarget:    breakTarget,
		ContinueTarget: continueTarget,
		Referenced:     false,
	}

	b.activeLabels = append(b.activeLabels, activeLabel)
	return activeLabel
}

func (b *Binder) popActiveLabel() {
	if len(b.activeLabels) > 0 {
		b.activeLabels = b.activeLabels[:len(b.activeLabels)-1]
	}
}

func (b *Binder) bindLabeledStatement(node *ast.LabeledStatement) {
	var preStatementLabel = b.createLoopLabel()
	var postStatementLabel = b.createBranchLabel()
	b.bind(node.Label)
	b.addAntecedent(preStatementLabel, b.currentFlow)
	b.pushActiveLabel(node.Label.Value, postStatementLabel, preStatementLabel)
	b.bind(node.Statement)
	b.popActiveLabel()
	if isNil(node.Statement) || !ast.IsDoStatement(node.Statement) {
		// do statement sets current flow inside bindDoStmt
		b.addAntecedent(postStatementLabel, b.currentFlow)
		b.currentFlow = b.finishFlowLabel(postStatementLabel)
	}
}

func (b *Binder) bindDestructuringTargetFlow(node ast.Expression) {
	if ast.IsBinaryExpression(node) {
		var expr = node.(*ast.BinaryExpression)
		if expr.Operator.Token == token.Equals {
			b.bindAssignmentTargetFlow(expr.Left)
			return
		}
	}

	b.bindAssignmentTargetFlow(node)
}

func (b *Binder) bindAssignmentTargetFlow(node ast.Node) {
	if b.isNarrowableReference(node) {
		b.currentFlow = b.createFlowAssignment(b.currentFlow, node)
	}
}

func (b *Binder) bindLogicalExpression(node *ast.BinaryExpression, trueTarget *FlowLabel, falseTarget *FlowLabel) {
	var preRightLabel = b.createBranchLabel()
	if node.Operator.Token == token.AmpersandAmpersand {
		b.bindCondition(node.Left, preRightLabel, falseTarget)
	} else {
		b.bindCondition(node.Left, trueTarget, preRightLabel)
	}
	b.currentFlow = b.finishFlowLabel(preRightLabel)
	b.bind(node.Operator)
	b.bindCondition(node.Right, trueTarget, falseTarget)
}

func (b *Binder) bindPrefixUnaryExpressionFlow(node *ast.PrefixUnaryExpression) {
	if node.Operator.Token == token.Exclamation {
		var saveTrueTarget = b.currentTrueTarget
		b.currentTrueTarget = b.currentFalseTarget
		b.currentFalseTarget = saveTrueTarget
		b.bindEachChild(node)
		b.currentFalseTarget = b.currentTrueTarget
		b.currentTrueTarget = saveTrueTarget
	} else {
		b.bindEachChild(node)
		if node.Operator.Token == token.PlusPlus || node.Operator.Token == token.MinusMinus {
			b.bindAssignmentTargetFlow(node.Operand)
		}
	}
}

func (b *Binder) bindPostfixUnaryExpressionFlow(node *ast.PostfixUnaryExpression) {
	b.bindEachChild(node)
	if node.Operator.Token == token.PlusPlus || node.Operator.Token == token.MinusMinus {
		b.bindAssignmentTargetFlow(node.Operand)
	}
}

func (b *Binder) bindBinaryExpressionFlow(node *ast.BinaryExpression) {
	var operator = node.Operator.Token
	if operator == token.AmpersandAmpersand || operator == token.BarBar {
		if b.isTopLevelLogicalExpression(node) {
			var postExpressionLabel = b.createBranchLabel()
			b.bindLogicalExpression(node, postExpressionLabel, postExpressionLabel)
			b.currentFlow = b.finishFlowLabel(postExpressionLabel)
		} else {
			b.bindLogicalExpression(node, b.currentTrueTarget, b.currentFalseTarget)
		}
	} else {
		b.bindEachChild(node)
		if operator.IsAssignmentOperator() && !ast.IsAssignmentTarget(node) {
			b.bindAssignmentTargetFlow(node.Left)
			if operator == token.Equals && ast.IsElementAccessExpression(node.Left) {
				var elementAccess = node.Left.(*ast.ElementAccessExpression)
				if b.isNarrowableOperand(elementAccess.Expression) {
					b.currentFlow = b.createFlowArrayMutation(b.currentFlow, node)
				}
			}
		}
	}
}

func (b *Binder) bindConditionalExpressionFlow(node *ast.ConditionalExpression) {
	var trueLabel = b.createBranchLabel()
	var falseLabel = b.createBranchLabel()
	var postExpressionLabel = b.createBranchLabel()
	b.bindCondition(node.Condition, trueLabel, falseLabel)
	b.currentFlow = b.finishFlowLabel(trueLabel)
	b.bind(node.QuestionTok)
	b.bind(node.WhenTrue)
	b.addAntecedent(postExpressionLabel, b.currentFlow)
	b.currentFlow = b.finishFlowLabel(falseLabel)
	b.bind(node.ColonTok)
	b.bind(node.WhenFalse)
	b.addAntecedent(postExpressionLabel, b.currentFlow)
	b.currentFlow = b.finishFlowLabel(postExpressionLabel)
}

func (b *Binder) bindInitializedVariableFlow(node *ast.VariableDeclaration) {
	b.currentFlow = b.createFlowAssignment(b.currentFlow, node)
}

func (b *Binder) bindVariableDeclarationFlow(node *ast.VariableDeclaration) {
	b.bindEachChild(node)
	if !isNil(node.Initializer) || ast.IsForOfStatement(node.Parent()) || ast.IsCatchClause(node.Parent()) {
		b.bindInitializedVariableFlow(node)
	}
}

func (b *Binder) bindCallExpressionFlow(node *ast.CallExpression) {
	// If the target of the call expression is a function expression or arrow function we have
	// an immediately invoked function expression (IIFE). Initialize the flowNode property to
	// the current control flow (which includes evaluation of the IIFE arguments).
	var expr = ast.SkipParenthesizedExpression(node.Expression)
	if ast.IsArrowExpression(expr) {
		b.bindEach(node.TypeArguments)
		b.bindEach(node.Arguments)
		b.bind(node.Expression)
	} else {
		b.bindEachChild(node)
	}
}

func (b *Binder) getContainerFlags(node ast.Node) ContainerFlags {
	switch n := node.(type) {
	case *ast.ClassDeclaration,
		*ast.BodyDeclaration,
		*ast.EnumDeclaration:
		return IsContainer

	case *ast.InterfaceDeclaration,
		*ast.AnnotationDeclaration:
		return IsContainer | IsInterface

	case *ast.Package:
		return IsContainer | IsControlFlowContainer | HasLocals

	case *ast.ConstructorDeclaration,
		*ast.MethodDeclaration:
		return IsContainer | IsControlFlowContainer | HasLocals | IsFunctionLike

	case *ast.ArrowFunctionDeclaration:
		return IsContainer | IsControlFlowContainer | HasLocals | IsFunctionLike | IsFunctionExpression

	case *ast.VariableDeclaration:
		if ast.IsFieldDeclaration(n.Parent()) && n.Initializer != nil {
			return IsControlFlowContainer
		}
		return CFNone

	case *ast.CatchClause,
		*ast.ForStatement,
		*ast.ForOfStatement,
		*ast.CaseBlock,
		*ast.BlockStatement:
		return IsBlockScopedContainer
	}

	return CFNone
}

func (b *Binder) addToContainerChain(next ast.Node) {
	if b.lastContainer != nil {
		setNodeNextContainer(b.lastContainer, next)
	}

	b.lastContainer = next
}

func (b *Binder) declareSymbolAndAddToSymbolTable(node ast.Declaration, symbolFlags SymbolFlags, symbolExcludes SymbolFlags) *Symbol {
	// Just call this directly so that the return type of this function stays "void".
	return b.declareSymbolAndAddToSymbolTableWorker(node, symbolFlags, symbolExcludes)
}

func (b *Binder) declareSymbolAndAddToSymbolTableWorker(node ast.Declaration, symbolFlags SymbolFlags, symbolExcludes SymbolFlags) *Symbol {
	switch b.container.(type) {
	// Modules, session files, and classes need specialized handling for how their
	// members are declared (for example, a member of a class will go into a specific
	// symbol table depending on if it is static or not). We defer to specialized
	// handlers to take care of declaring these child members.

	case *ast.ClassDeclaration,
		*ast.BodyDeclaration,
		*ast.InterfaceDeclaration,
		*ast.EnumDeclaration,
		*ast.AnnotationDeclaration:
		return b.declareTypeMember(node, symbolFlags, symbolExcludes)

	case *ast.MethodDeclaration,
		*ast.ConstructorDeclaration,
		*ast.ArrowFunctionDeclaration:
		// All the children of these container types are never visible through another
		// symbol (i.e. through another symbol's 'exports' or 'members').  Instead,
		// they're only accessed 'lexically' (i.e. from code that exists underneath
		// their container in the tree.  To accomplish this, we simply add their declared
		// symbol to the 'locals' of the container.  These symbols can then be found as
		// the type checker walks up the containers, checking them for matching names.
		return b.declareSymbol(GetLocalsOfNode(b.container), nil, node, symbolFlags, symbolExcludes)

	}

	return nil
}

func (b *Binder) isExportTypeMember(node ast.Node) bool {
	return ast.HasModifier(node, ast.MFStatic) || ast.IsEnumItemDeclaration(node) ||
		ast.IsFieldDeclaration(node) && ast.IsInterfaceLike(node.Parent())
}

func (b *Binder) declareTypeMember(node ast.Declaration, symbolFlags SymbolFlags, symbolExcludes SymbolFlags) *Symbol {
	var symbol = b.declareSymbol(GetSymbolOfNode(b.container).Members, GetSymbolOfNode(b.container), node, symbolFlags, symbolExcludes)
	if b.isExportTypeMember(node) || ast.IsAliasTag(node) && b.isExportTypeMember(ast.GetDocActiveNode(node)) {
		var exports = GetSymbolOfNode(b.container).Exports
		if !exports.HasSymbol(symbol.Name, symbol.Flags) {
			exports.AppendSymbol(symbol.Name, symbol)
		}
	}

	return symbol
}

func (b *Binder) bindAnonymousDeclaration(node ast.Declaration, symbolFlags SymbolFlags, name string) {
	var symbol = b.createSymbol(symbolFlags, name)
	b.addDeclarationToSymbol(symbol, node, symbolFlags)
}

func (b *Binder) nameEqualToFileName(node ast.Declaration) bool {
	var sourceFile = ast.GetSourceFileOfNode(node)
	if xpath.GetBaseFilename(sourceFile.Uri.Filename()) == b.getDeclarationName(node) {
		return true
	}
	return false
}

func (b *Binder) declarePackageMember(node ast.Declaration, symbolFlags SymbolFlags, symbolExcludes SymbolFlags) *Symbol {
	var symbol = b.declareSymbol(GetLocalsOfNode(b.container), GetSymbolOfNode(b.container), node, symbolFlags, symbolExcludes)
	// All declaration append to symbol exports
	GetSymbolOfNode(b.container).Exports.AppendSymbol(symbol.Name, symbol)
	return symbol
}

func (b *Binder) declareInterfaceLikeMember(node ast.Declaration, symbolFlags SymbolFlags, symbolExcludes SymbolFlags) *Symbol {
	return b.declareSymbol(GetSymbolOfNode(b.container).Exports, GetSymbolOfNode(b.container), node, symbolFlags, symbolExcludes)
}

func (b *Binder) declareClassLikeMember(node ast.Declaration, symbolFlags SymbolFlags, symbolExcludes SymbolFlags) *Symbol {
	var symbol = b.declareSymbol(GetSymbolOfNode(b.container).Members, GetSymbolOfNode(b.container), node, symbolFlags, symbolExcludes)
	if ast.HasModifier(node, ast.MFStatic) || ast.IsAliasTag(node) && isAliasTagMappedStaticDeclaration(node){
		var exports = GetSymbolOfNode(b.container).Exports
		if !exports.HasSymbol(symbol.Name, symbol.Flags) {
			exports.AppendSymbol(symbol.Name, symbol)
		}
	}

	return symbol
}

func (b *Binder) bindBlockScopedDeclaration(node ast.Declaration, symbolFlags SymbolFlags, symbolExcludes SymbolFlags) {
	switch b.blockScopeContainer.(type) {
	case *ast.Package:
		b.declarePackageMember(node, symbolFlags, symbolExcludes)
	case *ast.InterfaceDeclaration,
		*ast.AnnotationDeclaration:
		b.declareInterfaceLikeMember(node, symbolFlags, symbolExcludes)
	case *ast.ClassDeclaration,
		*ast.EnumDeclaration,
		*ast.BodyDeclaration:
		b.declareClassLikeMember(node, symbolFlags, symbolExcludes)
	default:
		if GetLocalsOfNode(b.blockScopeContainer) == nil {
			setNodeLocals(b.blockScopeContainer, NewSymbolTable())
			b.addToContainerChain(b.blockScopeContainer)
		}
		b.declareSymbol(GetLocalsOfNode(b.blockScopeContainer), nil, node, symbolFlags, symbolExcludes)
	}
}

// The binder visits every ast in the syntax tree so it is a convenient place to perform a single localized
// check for reserved words used as identifiers in strict mode code.
func (b *Binder) checkIdentifier(node *ast.Identifier) {
	//if node.OriginalKeywordKind >= token.FirstFutureReservedWord &&
	//	node.OriginalKeywordKind <= token.LastFutureReservedWord &&
	//	!util.IsIdentifierName(node) &&
	//	!util.IsInAmbientContext(node.ToNode()) {
	//	// Report error only if there are no parse errors in currentFile
	//	if len(b.currentFile.ParseDiagnostics) == 0 {
	//		b.currentFile.BindDiagnostics = append(b.currentFile.BindDiagnostics, util.CreateDiagnosticForNode(node.ToNode(),
	//			diagnostic.M_Identifier_expected_0_is_a_reserved_word, util.DeclNameToString(node.ToNode())))
	//	}
	//}
}

func (b *Binder) errorOnFirstToken(node ast.Node, message *diagnostic.Message, args ... interface{}) {
	var span = utilities.GetSpanOfTokenAtPosition(b.currentFile, node.Pos())
	diagnostic.AddDiagnosticToSourceFile(b.currentFile, diagnostic.BindDiagnostics,
		diagnostic.CreateFileDiagnostic(b.currentFile, span.Start, span.Length, message, args...))
}

func (b *Binder) bind(node ast.Node) bool {
	if isNil(node) {
		return false
	}
	node.SetParent(b.parent)
	// First we bind declaration nodes to a symbol if possible. We'll both create a symbol
	// and then potentially add the symbol to an appropriate symbol table. Possible
	// destination symbol tables are
	//
	//  1) The 'exports' table of the current container's symbol.
	//  2) The 'members' table of the current container's symbol.
	//  3) The 'locals' table of the current container.
	//
	// However, not all symbols will end up in any of these tables. 'Anonymous' symbols
	// (like TypeLiterals for example) will not be put in any table.
	b.bindWorker(node)
	// Then we recurse into the children of the ast to bind them as well. For certain
	// symbols we do specialized work when we recurse. For example, we'll keep track of
	// the current 'container' ast when it changes. This helps us know which symbol table
	// a local should go into for example. Since terminal nodes are known not to have
	// children, as an optimization we don't process those.
	if !ast.IsTokenNode(node) {
		var saveParent = b.parent
		b.parent = node
		var containerFlags = b.getContainerFlags(node)
		if containerFlags == CFNone {
			b.bindChildren(node)
		} else {
			b.bindContainer(node, containerFlags)
		}
		b.parent = saveParent
	}

	return false
}

func (b *Binder) trySetNodeAlias(node ast.Node) {
	for _, root := range node.GetDocuments() {
		for _, child := range root.(*ast.DocRoot).Children.Array() {
			child.SetParent(root)
			switch dn := child.(type) {
			case *ast.AliasTag:
				if alias := GetNodeAliasDeclaration(node); alias != nil && alias != dn {
					if dn.Name != nil {
						dn.Name.SetParent(node)
					}
					diagnostic.AddDiagnosticToSourceFile(b.currentFile, diagnostic.BindDiagnostics,
						utilities.CreateDiagnosticForNode(ast.FirstValidNode(dn.Name, node),
							diagnostic.M_Only_one_alias_tag_is_allowed))
					continue
				}

				setNodeAliasDeclaration(node, dn)
			}
		}
	}
}

func (b *Binder) bindWorker(node ast.Node) {
	b.trySetNodeAlias(node)

	switch n := node.(type) {
	case *ast.Identifier:
		b.checkIdentifier(n)
		if !isNil(b.currentFlow) {
			setNodeFlow(n, b.currentFlow)
		}
	case *ast.ThisExpression:
		if !isNil(b.currentFlow) {
			setNodeFlow(n, b.currentFlow)
		}
	case *ast.SelectorExpression:
		if !isNil(b.currentFlow) && b.isNarrowableReference(node) {
			setNodeFlow(n, b.currentFlow)
		}
	case *ast.TypeParameterDeclaration:
		b.declareSymbolAndAddToSymbolTable(n, TypeParameter, TypeParameterExcludes)
	case *ast.ParameterDeclaration:
		b.bindParameter(n)
	case *ast.VariableDeclaration:
		b.bindVariableDeclaration(n)
	case *ast.FieldDeclaration:
		b.bindFieldDeclaration(n)
	case *ast.EnumItemDeclaration:
		b.bindEnumItemDeclaration(n)
	case *ast.MethodDeclaration:
		b.bindMethodDeclaration(n)
	case *ast.ConstructorDeclaration:
		b.bindConstructorDeclaration(n)
	case *ast.ArrowFunctionDeclaration:
		b.bindArrowFunctionDeclaration(n)
	case *ast.ClassDeclaration:
		b.bindClassDeclaration(n)
	case *ast.BodyDeclaration:
		b.bindClassBodyDeclaration(n)
	case *ast.InterfaceDeclaration:
		b.bindInterfaceDeclaration(n)
	case *ast.AnnotationDeclaration:
		b.bindAnnotationDeclaration(n)
	case *ast.EnumDeclaration:
		b.bindEnumDeclaration(n)
	case *ast.SourceFile:
		b.bindSourceFile(n)
	case *ast.Package:
		b.bindPackage(n)
	}
}

func (b *Binder) bindSourceFile(node *ast.SourceFile) {
	// Set symbol count at current session file and clear.
	if b.currentFile != nil {
		b.currentFile.SymbolCount = b.symbolCount
		b.symbolCount = 0
	}
	b.currentFile = node
}

func (b *Binder) bindPackage(node *ast.Package) {
	b.bindAnonymousDeclaration(node, Package, node.PackageName)
}

func (b *Binder) bindClassBodyDeclaration(node *ast.BodyDeclaration) {
	b.bindAnonymousDeclaration(node, AnonymousClass, "__class")
}

func (b *Binder) bindInterfaceDeclaration(node *ast.InterfaceDeclaration) {
	if alias := GetNodeAliasDeclaration(node); alias != nil {
		b.bindBlockScopedDeclaration(alias, TypeAlias, TypeAliasExcludes)
	}
	b.bindBlockScopedDeclaration(node, Interface, InterfaceExcludes)
}

func (b *Binder) bindAnnotationDeclaration(node *ast.AnnotationDeclaration) {
	if alias := GetNodeAliasDeclaration(node); alias != nil {
		b.bindBlockScopedDeclaration(alias, TypeAlias, TypeAliasExcludes)
	}
	b.bindBlockScopedDeclaration(node, Annotation, AnnotationExcludes)
}

func (b *Binder) bindClassDeclaration(node *ast.ClassDeclaration) {
	if alias := GetNodeAliasDeclaration(node); alias != nil {
		b.bindBlockScopedDeclaration(alias, TypeAlias, TypeAliasExcludes)
	}
	b.bindBlockScopedDeclaration(node, Class, ClassExcludes)
}

func (b *Binder) bindEnumDeclaration(node *ast.EnumDeclaration) {
	if alias := GetNodeAliasDeclaration(node); alias != nil {
		b.bindBlockScopedDeclaration(alias, TypeAlias, TypeAliasExcludes)
	}
	b.bindBlockScopedDeclaration(node, Enum, EnumExcludes)
}

func (b *Binder) bindVariableDeclaration(node *ast.VariableDeclaration) {
	b.bindBlockScopedDeclaration(node, BlockScopedVariable, BlockScopedVariableExcludes)
}

func (b *Binder) bindParameter(node *ast.ParameterDeclaration) {
	b.declareSymbolAndAddToSymbolTable(node, FunctionScopedVariable, ParameterExcludes)
}

func (b *Binder) bindArrowFunctionDeclaration(node *ast.ArrowFunctionDeclaration) {
	if b.currentFlow != nil {
		setNodeFlow(node, b.currentFlow)
	}

	var bindingName = "__function"
	b.bindAnonymousDeclaration(node, Function, bindingName)
}

func (b *Binder) bindFieldDeclaration(node *ast.FieldDeclaration) {
	//if b.currentFlow != nil && b.isObjectLiteralOrClassExpressionMethod(ast) {
	//	ast.FlowNode = b.currentFlow
	//}

	if alias := GetNodeAliasDeclaration(node); alias != nil {
		b.declareSymbolAndAddToSymbolTable(alias, FieldAlias, FieldAliasExcludes)
	}
	b.declareSymbolAndAddToSymbolTable(node, Field, FieldExcludes)
}

func (b *Binder) bindEnumItemDeclaration(node *ast.EnumItemDeclaration) {
	//if b.currentFlow != nil && b.isObjectLiteralOrClassExpressionMethod(ast) {
	//	ast.FlowNode = b.currentFlow
	//}

	if alias := GetNodeAliasDeclaration(node); alias != nil {
		b.declareSymbolAndAddToSymbolTable(alias, FieldAlias, FieldAliasExcludes)
	}
	b.declareSymbolAndAddToSymbolTable(node, EnumMember, EnumMemberExcludes)

}

func (b *Binder) bindConstructorDeclaration(node *ast.ConstructorDeclaration) {
	b.declareSymbolAndAddToSymbolTable(node, Constructor, None)
}

func (b *Binder) bindMethodDeclaration(node *ast.MethodDeclaration) {
	//if b.currentFlow != nil && b.isObjectLiteralOrClassExpressionMethod(ast) {
	//	ast.FlowNode = b.currentFlow
	//}

	if alias := GetNodeAliasDeclaration(node); alias != nil {
		b.declareSymbolAndAddToSymbolTable(alias, MethodAlias, MethodAliasExcludes)
	}
	b.declareSymbolAndAddToSymbolTable(node, Method, MethodExcludes)

}

// reachability checks
func (b *Binder) checkUnreachable(node ast.Node) bool {
	if b.currentFlow == nil || !IsUnreachableFlow(b.currentFlow) {
		return false
	}

	if b.currentFlow == b.unreachableFlow {


		var reportError = ast.IsStatement(node) && !ast.IsDeclarationStatement(node) && !ast.IsEmptyStatement(node) ||
			// report error on all statements except empty ones
			ast.IsClassDeclaration(node)
			// report error on class declarations

		if reportError {
			b.currentFlow = b.reportedUnreachableFlow

			// unreachable code is reported if
			// - user has explicitly asked about it AND
			// - statement is in not ambient context (statements in ambient context is already an error
			//   so we should not report extras) AND
			//   - ast is not variable statement OR
			//   - ast is block scoped variable statement OR
			//   - ast is not block scoped variable statement and at least one variable declaration has initializer
			//   Rationale we don't want to report errors on non-initialized var's since they are hoisted
			//   On the other side we do want to report errors on non-initialized 'vars' because of TDZ
			b.errorOnFirstToken(node, diagnostic.M_Unreachable_code_detected)
			//
			//var reportUnreachableCode = !b.options.AllowUnreachableCode &&
			//	!util.IsInAmbientContext(ast) && (ast.Kind != token.VariableStatement || ast.ToVariableStatement().DeclarationList.Initializer != nil)
			//
			//if reportUnreachableCode {
			//	b.errorOnFirstToken(ast, diagnostic.M_Unreachable_code_detected)
			//}
		}
	}
	return true
}

func isNil(i interface{}) bool {
	ret := i == nil
	if !ret {
		vi := reflect.ValueOf(i)
		kind := reflect.ValueOf(i).Kind()
		if kind == reflect.Slice ||
			kind == reflect.Map ||
			kind == reflect.Chan ||
			kind == reflect.Interface ||
			kind == reflect.Func ||
			kind == reflect.Ptr {
			return vi.IsNil()
		}
	}

	return ret
}